#if !LITE
using System;
using System.Text;
using System.IO;
using System.Collections;

using Word = Microsoft.Office.Interop.Word;
using Core = Microsoft.Office.Core;
using iTextSharp.text;

namespace iPdf
{
    class DocConverter: OfficeConverter
    {
        #region Fields
        /// <summary>
        /// The logger
        /// </summary>
        
        private static Word.Application word ;
        private static Word.Document document;
        
        private const Word.WdInformation wdFirstLineNo = Word.WdInformation.wdFirstCharacterLineNumber;
        private const Word.WdInformation wdHorizontalPos = Word.WdInformation.wdHorizontalPositionRelativeToPage;
        private const Word.WdInformation wdVerticalPos = Word.WdInformation.wdVerticalPositionRelativeToPage;
        private const Word.WdInformation wdEndPageNo = Word.WdInformation.wdActiveEndPageNumber;
        private const Word.WdGoToItem wdGoToPage = Word.WdGoToItem.wdGoToPage;
        private const Word.WdGoToItem wdGoToLine = Word.WdGoToItem.wdGoToLine;
        private const Word.WdGoToDirection wdGoToPrevious = Word.WdGoToDirection.wdGoToPrevious;
        private const Word.WdGoToDirection wdGoToNext = Word.WdGoToDirection.wdGoToNext;


        #endregion

        #region Properties
        private static int SelectionLineNo
        {
            get
            {
                return (int)word.Selection.get_Information(wdFirstLineNo);
            }
        }

        private static int SelectionPageNo
        {
            get
            {
                return (int)word.Selection.get_Information(wdEndPageNo);
            }
        }

        private static float SelectionHorizontalPosition
        {
            get
            {
                return (float)word.Selection.get_Information(wdHorizontalPos);
            }
        }

        private static float SelectionVerticalPosition
        {
            get
            {
                return (float)word.Selection.get_Information(wdVerticalPos);
            }
        }

        protected override string ApplicationName
        {
            get
            {
                return "Microsoft Word";
            }
        }
        #endregion

        #region Methods

        #region Helpers methods
        private static void GoToPrevious(object gotoItem)
        {
            GoToPrevious(gotoItem, Global.NullObject);
        }

        private static void GoToPrevious(object gotoItem, object gotoCount)
        {
            object gotoDirection = Word.WdGoToDirection.wdGoToPrevious;
            word.Selection.GoTo(ref gotoItem, ref gotoDirection, ref gotoCount, ref Global.NullObject);
        }

        private static void GoToNext(object gotoItem)
        {
            GoToNext(gotoItem, Global.NullObject);
        }

        private static void GoToNext(object gotoItem, object gotoCount)
        {
            object gotoDirection = Word.WdGoToDirection.wdGoToNext;
            word.Selection.GoTo(ref gotoItem, ref gotoDirection, ref gotoCount, ref Global.NullObject);
        }

        private static Word.Range GetRangeAt(int charIndex)
        {
            object start = charIndex;
            return document.Range(ref start, ref start);
        }

        private static Word.Range GoToCharAt(int charIndex)
        {
            Word.Range range = GetRangeAt(charIndex);
            range.Select();
            return range;
        }

        private static void MoveCursorToLineEnd()
        {
            MoveCursorToLineEnd(false);
        }

        private static void MoveCursorToLineEnd(bool extend)
        {
            object moveUnit = Word.WdUnits.wdLine;
            object moveType = Word.WdMovementType.wdMove;
            if (extend)
                moveType = Word.WdMovementType.wdExtend;
            word.Selection.EndKey(ref moveUnit, ref moveType);
        }

        private static void MoveCursorToLineHome()
        {
            MoveCursorToLineHome(false);
        }

        private static void MoveCursorToLineHome(bool extend)
        {
            object moveUnit = Word.WdUnits.wdLine;
            object moveType = Word.WdMovementType.wdMove;
            if (extend)
                moveType = Word.WdMovementType.wdExtend;
            word.Selection.HomeKey(ref moveUnit, ref moveType);
        }

        private static float GetCurrentLineHeight(bool isLastLine)
        {
            //select current line
            object moveUnit = Word.WdUnits.wdLine;
            object moveType = Word.WdMovementType.wdMove;
            word.Selection.HomeKey(ref moveUnit, ref moveType);
            moveType = Word.WdMovementType.wdExtend;
            word.Selection.EndKey(ref moveUnit, ref moveType);

            Word.Paragraph para = word.Selection.Paragraphs[1];
            float spacing = 2 * para.LineSpacing;
            if (isLastLine)
            {
                spacing += para.LineUnitAfter;
            }

            Word.Font rangeFont = word.Selection.Range.Font;
            if (rangeFont != null && rangeFont.Size > 0)
            {
                return spacing + GetTotalFontSize(new TataFont(rangeFont));
            }

            float maxHeight = spacing;
            foreach (Word.Range character in word.Selection.Characters)
            {
                Word.Font font = character.Font;
                if (font == null)
                {
                    continue;
                }

                float height = spacing + GetTotalFontSize(new TataFont(font));
                if (height > maxHeight)
                {
                    maxHeight = height;
                }
            }

            return maxHeight;
        }
        #endregion

        protected override void OpenFile()
        {
            //for MS Word is very picky over the printer papersize selection, 
            //we open a new application for each file. It may take more time,
            //but the correctness of the result will be guaranteed 
            OpenApplication();
            object docName = workItem.Source;
            document = word.Documents.Open(ref docName, ref Global.FalseValue,
                                                  ref Global.TrueValue, ref Global.FalseValue, ref Global.NullObject,
                                                  ref Global.NullObject, ref Global.TrueValue, ref Global.NullObject,
                                                  ref Global.NullObject, ref Global.NullObject, ref Global.NullObject,
                                                  ref Global.FalseValue, ref Global.NullObject, ref Global.NullObject,
                                                  ref Global.TrueValue, ref Global.NullObject);

            document.Activate();
        }

        protected override void CloseFile()
        {
            if (document != null)
            {
                ((Word._Document)document).Close(ref Global.FalseValue, ref Global.NullObject, ref Global.NullObject);
                document = null;

                //for MS Word is very picky over the printer papersize selection, 
                //we open a new application for each file. It may take more time,
                //but the correctness of the result will be guaranteed
                Helper.Sleep(Global.RestTime);
                CloseApplication();
            }
        }

        protected override void PrintOut()
        {
            PdfCreator.PrintOut(document);
        }

        protected override void ExtractBookmarks()
        {
            if (document == null || !workItem.Bookmark || document.Paragraphs.Count == 0)
            {
                return;
            }

            AbsolutePosition.PageWidth = document.PageSetup.PageWidth;
            AbsolutePosition.PageHeight = document.PageSetup.PageHeight;
            bool firstNode = true;
            BookmarkNode prevNode = null;
            Word.WdOutlineLevel prevLevel = Word.WdOutlineLevel.wdOutlineLevel1;
            Word.Range range ;
            AbsolutePosition position;
            char pageBreak = '\f';
            char[] whiteChars = new char[]{' ', '\t', '\n', '\r'};
            string paraText = string.Empty;
            Word.WdOutlineLevel curLevel;
            BookmarkNode node = null;

            Word.Paragraphs paragraphs = document.Paragraphs;
            
            foreach (Word.Paragraph para in paragraphs)
            {
                curLevel = para.OutlineLevel;

                if (curLevel != Word.WdOutlineLevel.wdOutlineLevelBodyText)
                {
                    range = para.Range;
                    GoToCharAt(range.Start);
                    position = new AbsolutePosition(0, SelectionVerticalPosition,
                                                    SelectionPageNo);
                    paraText = range.Text;
                    //remove the leading page break and accordingly, go to next page
                    while (paraText.Length > 0 &&
                        paraText[0] == pageBreak
                    )
                    {
                        GoToNext(wdGoToPage);
                        position = new AbsolutePosition(0, SelectionVerticalPosition,
                                                    SelectionPageNo);
                        paraText = paraText.Substring(1);
                    }

                    paraText = paraText.Trim();
                    if (paraText.Length != 0)
                    {
                        node = CreateBookmarkByPosition(position, paraText, (int)curLevel,
                            new PageSetup(word.Selection.PageSetup));

                        if (firstNode == true)
                        {
                            bookmarks.Add(node.Map);
                            firstNode = false;
                        }
                        else
                        {
                            if (curLevel > prevLevel)
                            {
                                prevNode.AddKid(node);
                            }
                            else if (curLevel == prevLevel)
                            {
                                if (!prevNode.IsTopLevelNode)
                                {
                                    prevNode.ParentNode.AddKid(node);
                                }
                                else
                                {
                                    bookmarks.Add(node.Map);
                                }
                            }
                            else
                            {
                                while (curLevel <= prevLevel)
                                {
                                    if (prevNode.IsTopLevelNode)
                                    {
                                        //it's the top level node, break out
                                        break;
                                    }

                                    prevNode = prevNode.ParentNode;
                                    prevLevel = (Word.WdOutlineLevel) prevNode.Level;
                                }//while

                                if (prevLevel < curLevel)
                                {
                                    prevNode.AddKid(node);
                                }
                                else
                                {
                                    bookmarks.Add(node.Map);
                                }
                            }//else
                        }

                        //update the previous curLevel
                        prevLevel = curLevel;
                        //update previous node
                        prevNode = node;
                    }
                }
            }

        }

        private static Rectangle[] GetHyperlinkRectangles(Word.Hyperlink link, ref int linkPageNo)
        {
            System.Collections.Generic.List<Rectangle> rectList = new 
                            System.Collections.Generic.List<Rectangle>();

            float startX, startY, endX, endY;
            Word.Range startPoint = GoToCharAt(link.Range.Start);
            Word.Range endPoint = GetRangeAt(link.Range.End);

            int startPageNo = SelectionPageNo;
            if (startPageNo <= 0)
            {
                return null;
            }

            linkPageNo = startPageNo;

            endPoint.Select();
            int endPageNo = SelectionPageNo;
            while (startPageNo < endPageNo)
            {
                GoToPrevious(wdGoToLine, SelectionLineNo);
                MoveCursorToLineEnd();
                --endPageNo;
            }
            //update end point
            endPoint = word.Selection.Range;
            int endLineNo = SelectionLineNo;
            startPoint.Select();
            int startLineNo = SelectionLineNo;

            Word.PageSetup pageSetup = word.Selection.PageSetup;
            while (startLineNo < endLineNo)
            {
                startX = SelectionHorizontalPosition;
                startY = SelectionVerticalPosition;
                float rightOffset = word.Selection.Paragraphs.RightIndent + pageSetup.RightMargin;
                endX = pageSetup.PageWidth - rightOffset;

                //goto next line
                GoToNext(wdGoToLine);
                endY = SelectionVerticalPosition;

                Rectangle rect = CreateRectangle(startX, endX, startY, endY, 
                                        new PageSetup(pageSetup), Global.TextLinkSpecificOffset);
                if (rect != null)
                {
                    rectList.Add(rect);
                }
                ++startLineNo;
                MoveCursorToLineHome();
            }

            if (word.Selection.Start == endPoint.Start)
            {
                //it's at the beginning of the line or an empty range, nothing to do, return
                return rectList.ToArray();
            }

            startX = SelectionHorizontalPosition;
            startY = SelectionVerticalPosition;

            if (startY == 77.0f)
            {
                startY = 126.5f;
            }

            //go to line end
            MoveCursorToLineEnd();
            if (word.Selection.Start != endPoint.Start)
            {
                //not at the end of the line, select the end point
                //this is a work around for MS Word's positioning bug when at the 
                //end of the line or at the beginning of the line
                endPoint.Select();
            }

            endX = SelectionHorizontalPosition;

            //goto next line
            GoToNext(wdGoToLine);
            endPageNo = SelectionPageNo;
            if (endPageNo > startPageNo)
            {
                endY = startY + GetCurrentLineHeight(false);
            }
            else
            {
                int lastLineNo = (int)endPoint.get_Information(wdFirstLineNo);

                //this is a workaround for MS Word's minor bug annoying positioning bug
                MoveCursorToLineEnd();
                int nextLineNo = SelectionLineNo;
                if (lastLineNo == nextLineNo)
                {
                    //fail to move, means we're at the end of the document
                    endY = startY + GetCurrentLineHeight(true);
                }
                else
                {
                    endY = SelectionVerticalPosition;
                }
            }

            Rectangle lastRect = CreateRectangle(startX, endX, startY, endY,
                                    new PageSetup(pageSetup), Global.TextLinkSpecificOffset);
            if (lastRect != null)
            {
                rectList.Add(lastRect);
            }

            return rectList.ToArray();
        }

        protected override void ExtractHyperlinks()
        {
            if (!workItem.Hyperlink || document == null)
            {
                return;
            }

            hyperlinks = new ArrayList();

            System.Collections.Generic.List<Rectangle> rectList = new 
                    System.Collections.Generic.List<Rectangle>();
            foreach (Word.Hyperlink hyperlink in document.Hyperlinks)
            {
                rectList.Clear();
                
                int linkPageNo = 0;

                string address;
                try
                {
                    address = hyperlink.Address;
                }
                catch
                {
                    //something went wrong, we simply goto next hyperlink
                    continue;
                }

                switch (hyperlink.Type)
                {
                    case Core.MsoHyperlinkType.msoHyperlinkShape:
                        {
                            Word.Shape shape = hyperlink.Shape;
                            shape.Select(ref Global.NullObject);
                            int pageNo = SelectionPageNo;
                            linkPageNo = pageNo;
                            Word.PageSetup pageSetup = word.Selection.PageSetup;
                            float startX = SelectionHorizontalPosition;
                            float startY = SelectionVerticalPosition;
                            double A = shape.Rotation * Math.PI / 180.0f;
                            double cosA = Math.Abs(Math.Cos(A));
                            double sinA = Math.Abs(Math.Sin(A));
                            float endX = startX +  (float)(shape.Width * cosA + shape.Height * sinA);
                            float endY = startY +  (float)(shape.Height * cosA + shape.Width * sinA);

                            Rectangle rect = CreateRectangle(startX, endX, startY, endY, new PageSetup(pageSetup));
                            if (rect != null)
                            {
                                rectList.Add(rect);
                            }
                        }
                        break;

                    case Core.MsoHyperlinkType.msoHyperlinkRange:
                        {
                            Rectangle[] rects = GetHyperlinkRectangles(hyperlink, ref linkPageNo);
                            if (rects != null)
                            {
                                rectList.AddRange(rects);
                            }
                        }
                        break;
                }//switch

                Hyperlink link = null;
                if (address == null || address.Length == 0)
                {
                    //it's a local destination, bookmark or heading
                    object index = hyperlink.SubAddress;
                    Word.Bookmark bookmark ;
                    try
                    {
                        bookmark = document.Bookmarks.get_Item(ref index);
                    }
                    catch
                    {
                        //invalid bookmark, we continue to next hyperlink
                        continue;
                    }
                    if (bookmark == null)
                    {
                        continue;
                    }
                    Word.Range startPoint = bookmark.Range;
                    startPoint.Select();
                    float Y = SelectionVerticalPosition;
                    CoordinateConverter.ConvertVerticalCoordinate(ref Y, new PageSetup( startPoint.PageSetup) , Global.BookmarkSpecificOffset);
                    link = new LocalDestination(linkPageNo, SelectionPageNo, Y);
                }
                else if (Hyperlink.IsWebLink(address))
                {
                    //it's a web link
                    link = new RemoteLink(linkPageNo, address);
                }
                else if (Hyperlink.IsMailToLink(address))
                {
                    //it's a mail to link
                    link = new MailTo(linkPageNo, address, hyperlink.EmailSubject);
                }
                else
                {
                    try
                    {
                        //it's a remote file
                        FileInfo fileInfo = new FileInfo(document.FullName);
                        string oriWorkingDir = System.Environment.CurrentDirectory;
                        System.Environment.CurrentDirectory = fileInfo.DirectoryName;
                        fileInfo = new FileInfo(address);
                        if (fileInfo.Exists)
                        {
                            address = "file:///" + fileInfo.FullName;
                            address = address.Replace('\\', '/');
                            link = new RemoteLink(linkPageNo, address);
                            System.Environment.CurrentDirectory = oriWorkingDir;
                        }
                    }
                    catch
                    {
                        //it's a dead link, we simply ignore it
                    }
                }

                if (link != null)
                {
                    link.AddRectangles(rectList.ToArray());

                    hyperlinks.Add(link);
                }
            }//for
        }

        protected override void OpenApplication()
        {
            if (word == null)
            {
                word = new Word.ApplicationClass();
                word.Visible = false;
                word.DisplayAlerts = Microsoft.Office.Interop.Word.WdAlertLevel.wdAlertsNone;
            }
        }

        protected override void CloseApplication()
        {
            if (word != null)
            {
                ((Word._Application)word).Quit(ref Global.FalseValue, ref Global.NullObject, ref Global.NullObject);
                System.Runtime.InteropServices.Marshal.ReleaseComObject(word);
                word = null;
            }
        }

        #endregion
    }//DocConverter



}//iPdf

#endif